#ifndef _DevBuffer_H_
#define _DevBuffer_H_

struct alloc_info_struct
{
  int ptr; int bytes_num; bool busy; const char *szSource;
  unsigned Size() { return sizeof(*this); }

	void GetMemoryUsage(ICrySizer *pSizer ) const {}
};

typedef uint16 DevBufferAllocHdl;
static const uint16 InvalidDevBufferAllocHdl = (uint16)-1;

struct DevBufferAllocChunk
{
	DevBufferAllocHdl freePrevIdx;
	DevBufferAllocHdl freeNextIdx;
	DevBufferAllocHdl addrPrevIdx;
	DevBufferAllocHdl addrNextIdx;

	UINT_PTR ptr;
	unsigned size : 31;
	unsigned busy : 1;

#ifndef _RELEASE
	const char* source;
#endif
};

class CDevBufferAllocTree
{
public:
	void Init(UINT_PTR capacity);

	DevBufferAllocHdl Allocate(size_t sz, const char* source);
	bool Free(DevBufferAllocHdl addr);

	ILINE UINT_PTR Resolve(DevBufferAllocHdl hdl)
	{
		return m_chunks[hdl].ptr;
	}

	ILINE const char* GetSourceOf(DevBufferAllocHdl hdl)
	{
#ifndef _RELEASE
		return m_chunks[hdl].source;
#else
		return "";
#endif
	}

private:
	enum
	{
		NumBuckets = 20,	// 1MB
		Alignment = 32,
	};

	static const uint16 InvalidChunkID = (uint16)-1;

private:
	uint16 AllocateChunk();
	void ReleaseChunk(uint16 id);
	void LinkFreeChunk(uint16 id);
	void UnlinkFreeChunk(uint16 id)
	{
		DevBufferAllocChunk& chunk = m_chunks[id];
		m_chunks[chunk.freePrevIdx].freeNextIdx = chunk.freeNextIdx;
		m_chunks[chunk.freeNextIdx].freePrevIdx = chunk.freePrevIdx;
	}


private:
	uint16 m_freeBuckets[NumBuckets];
	std::vector<DevBufferAllocChunk> m_chunks;
	std::vector<uint16> m_unusedChunks;

	uint32 m_capacity;
	uint32 m_available;
};

#define POOL_SIZE_VB 512*1024   // size of VB pool in bytes
#define POOL_SIZE_IB 256*1024   // size of VB pool in bytes

union UD3DBuffer
{
#ifndef NULL_RENDERER
  D3DVertexBuffer *m_pVB;
  D3DIndexBuffer *m_pIB;
#else
  void *m_pVB;
  void *m_pIB;
#endif
};

struct SDevPool
{
  uint32 m_nPoolSize;
  uint32 m_nAllocatedSize;
  UD3DBuffer m_D3DBuf;
	CDevBufferAllocTree m_AllocTree;
#if BUFFER_ENABLE_DIRECT_ACCESS
	byte *pBasePtr; //base pointer to buffer
#endif

	SDevPool(uint32 nSize)
  {
    m_nPoolSize = nSize;
    m_nAllocatedSize = 0;
    m_D3DBuf.m_pVB = NULL;
		m_AllocTree.Init(nSize);
#if BUFFER_ENABLE_DIRECT_ACCESS
		pBasePtr = (byte*)0xffffffff;
#endif
		m_AllocTree.Init(nSize);
  }

	void GetMemoryUsage( ICrySizer *pSizer ) const
	{
	}
};

struct SDevPoolItem
{
  int16  m_nPool;
	DevBufferAllocHdl m_allocHdl;
  int32 m_nBufOffset;

  SDevPoolItem()
  {
    m_nPool = -1;
		m_allocHdl = InvalidDevBufferAllocHdl;
    m_nBufOffset = 0;
  }
};

#define MAX_STAGING_BUFFERS 512

struct SDevBuffer
{
  uint32 m_nSize;
  SDevPoolItem *m_pPoolItem;    // In case of pool only
  UD3DBuffer m_D3DBuf;  // In case of pool-less

# if BUFFER_ENABLE_DIRECT_ACCESS
	byte *pBasePtr; //base pointer to buffer
# endif

#if defined(DIRECT3D10)
  bool m_bTempBuf;
  int m_nStagedOffset;
  int m_nStagedSize;
  int m_nDevOffset;
  void *m_pStagedVB;
  uint32 m_nLockFlags;
#endif

  SDevBuffer()
  {
    m_pPoolItem = NULL;
#   if BUFFER_ENABLE_DIRECT_ACCESS
		pBasePtr = (byte*)0xffffffff;
#   endif
#   if defined(DIRECT3D10)
    m_bTempBuf = false;
    m_nStagedOffset = 0;
    m_nStagedSize = 0;
    m_nDevOffset = 0;
    m_nLockFlags = 0;
    m_pStagedVB = NULL;
#   endif
    m_D3DBuf.m_pVB = NULL;
  }
  ~SDevBuffer();

	void GetMemoryUsage(ICrySizer *pSizer) const{}
};

class CDevBufferMan
{
  friend class CD3D9Renderer;
  friend class CRenderMesh;

  std::vector<SDevBuffer> m_VBs;
  std::vector<SDevBuffer> m_IBs;

  std::vector<uint32> m_FreeVBSlots;
  std::vector<uint32> m_FreeIBSlots;

  std::vector<SDevPool> m_VBPools;
  std::vector<SDevPool> m_IBPools;

  size_t m_activeVBs;
  size_t m_activeIBs;
	
#if BUFFER_NO_LOCK_ON_UPDATE
	enum EPoolType
	{
		POOL_VERTEX=0,
		POOL_INDEX
	};

	struct delayDeleteInfo
	{
		SDevPoolItem poolItem;
		int deleteFrame;
		int size;
		EPoolType poolType;
	};

	std::vector<delayDeleteInfo> m_delayDeleteList;
	static const uint32 DELAY_DELETE_NUM_FRAMES = 3;
#endif //NO_LOCK_ON_UPDATE

	CryCriticalSection m_Lock;

	// Staging Buffers for D3D11 support 
# ifdef DIRECT3D10
  int m_nMaxStagingBuffers;
  int m_nCurStagedVB;
  int m_nCurStagedIB;
  void *m_pVBTemp[MAX_STAGING_BUFFERS];
  void *m_pIBTemp[MAX_STAGING_BUFFERS];
# endif

private:

  int32 CreateDevVB(int32 nSizeBuf, SDevBuffer& DB);
  int32 CreateDevVBInPool(int32 nSizeBuf, SDevBuffer& DB);
  int32 CreateDevIB(int32 nSizeBuf, SDevBuffer& DB);
  int32 CreateDevIBInPool(int32 nSizeBuf, SDevBuffer& DB);
  bool  ReleaseDevVB(SDevBuffer& DB);
  bool  ReleaseDevVBInPool(SDevBuffer& DB);
  bool  ReleaseDevIB(SDevBuffer& DB);
  bool  ReleaseDevIBInPool(SDevBuffer& DB);
#if defined (DIRECT3D9) || defined (DIRECT3D10)
	void	UnsetStreamSources(D3DVertexBuffer *pVB);
	void	UnsetIndices(D3DIndexBuffer *pIB);
#endif
  int32 AllocateVBSlot(SDevBuffer& DB);
  int32 AllocateIBSlot(SDevBuffer& DB);
  bool  AllocateChunk(int32 nSizeBuf, SDevPool& Pool, SDevPoolItem *pPoolItem);
	bool	PoolAllocateChunkVB(int32 nSizeBuf, SDevPoolItem *pPoolItem, int &poolIndex);
	bool	PoolAllocateChunkIB(int32 nSizeBuf, SDevPoolItem *pPoolItem, int &poolIndex);
	
	inline  SDevBuffer *GetDevVB(int32 nVB)
	{
		if (nVB < 0 || nVB >= (int32)m_VBs.size())
			return NULL;
		SDevBuffer *pDB = &m_VBs[nVB];
		assert(pDB->m_nSize > 0);
		return pDB;
	}

  inline SDevBuffer *GetDevIB(int32 nIB)
	{
		if (nIB < 0 || nIB >=(int32)m_IBs.size())
			return NULL;
		SDevBuffer *pDB = &m_IBs[nIB];
		assert(pDB->m_nSize > 0);
		return pDB;
	}

  void ValidateIBPools();
  void ValidateVBPools();

public:
  int32 CreateVB(int32 nSize);
  int32 CreateIB(int32 nSize);
  bool  ReleaseVB(int32 nVB);
  bool  ReleaseIB(int32 nIB); 
  bool Cleanup(); 

  void *LockVB(int32 nVB, int32 nOffset=0, int32 nSize=0, uint32 nFlags=0);
  void  UnlockVB(int32 nVB);

  void *LockIB(int32 nIB, int32 nOffset=0, int32 nSize=0, uint32 nFlags=0);
  void  UnlockIB(int32 nIB);

  void *UpdateVB(int32 nVB, const void *pData, int32 nSize, int32 nOffs=0, bool stall=true);
  void *UpdateIB(int32 nIB, const void *pData, int32 nSize, int32 nOffs=0, bool stall=true);

#if defined(DIRECT3D10)
	void LockDevMan();
	void UnlockDevMan();
#else
	//not needed without staging buffers
	void LockDevMan(){}
	void UnlockDevMan(){}
#endif

  CVertexBuffer *CreateVBuffer(int32 nVerts, EVertexFormat eVF, const char *szName);
  CIndexBuffer *CreateIBuffer(int32 nInds, const char *szName);
  void* UpdateVBuffer(CVertexBuffer *pVB, void *pVerts, int32 nVerts);
  void* UpdateIBuffer(CIndexBuffer *pIB, void *pInds, int32 nInds);
  void  ReleaseVBuffer(CVertexBuffer* pVB);
  void  ReleaseIBuffer(CIndexBuffer* pIB);

  void Init();
  void Invalidate();
	void Update();

#ifndef NULL_RENDERER
  void *LockVB(D3DVertexBuffer *pVB, int32 nOffset, uint32 nFlags);
  void  UnlockVB(D3DVertexBuffer *pVB);
  void *LockIB(D3DIndexBuffer *pIB, int32 nOffset, uint32 nFlags);
  void  UnlockIB(D3DIndexBuffer *pIB);

  D3DVertexBuffer *GetD3DVB(int32 nVB, int32* nOffs);
  D3DIndexBuffer  *GetD3DIB(int32 nIB, int32* nOffs);
#endif

	void GetMemoryUsage( ICrySizer *pSizer ) const
	{
		pSizer->AddObject(m_VBs);
		pSizer->AddObject(m_IBs);
		pSizer->AddObject(m_FreeVBSlots);
		pSizer->AddObject(m_FreeIBSlots);
		pSizer->AddObject(m_VBPools);		
		pSizer->AddObject(m_IBPools);		
	}
};

#if defined(DIRECT3D10) && !defined(PS3)
struct WrappedDX11Buffer
{
	WrappedDX11Buffer()
	: m_pBuffer(0)
	, m_pSRV(0)
	, m_numElements(0)
	{
	}

	~WrappedDX11Buffer();

	WrappedDX11Buffer(const WrappedDX11Buffer& src);
	WrappedDX11Buffer& operator=(const WrappedDX11Buffer& rhs);

	void Create(unsigned int numElements, unsigned int elementSize, DXGI_FORMAT elementFormat, const void* pData, D3D11_BIND_FLAG bindFlag);

	ID3D11Buffer* m_pBuffer;
	ID3D11ShaderResourceView* m_pSRV;
	int m_numElements;
};
#endif

#endif  _D3DBuffer_H_
